package org.gjy.m8.bucket;

import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicLong;

/**
 * Fixed‑Window 计数器（每个窗口内最大 N 次请求）。
 * <p>
 * 2.1 思路
 * 对每个 key 维护 计数 + 窗口起始时间。
 * 当请求到达：
 * 若 当前时间 已超过窗口结束，则 计数归 0 并 更新窗口起点。
 * 再检查 计数 < limit → 计数 +1，返回 true；否则返回 false。
 *
 * @author gjy
 * @version 1.0
 * @since 2025-10-13 17:53:06
 */
public class FixedWindowRateLimiter<K> implements RateLimiter<K> {

    /**
     * 每个 key 对应的计数信息
     */
    private static class Window {
        final long limit;          // 本窗口最大请求数
        final long windowMs;       // 窗口长度（毫秒）
        volatile long startTs;     // 当前窗口起始时间戳（毫秒）
        final AtomicLong counter = new AtomicLong(0L);

        Window(long limit, long windowMs) {
            this.limit = limit;
            this.windowMs = windowMs;
            this.startTs = System.currentTimeMillis();
        }
    }

    /**
     * key -> Window 的并发映射
     */
    private final ConcurrentMap<K, Window> windows;
    private final long limit;      // 默认限流阈值
    private final long windowMs;   // 默认窗口大小（毫秒）

    /**
     * @param limit    每窗口最多允许的请求数
     * @param windowMs 窗口长度，毫秒（如 1000 = 1s）
     */
    public FixedWindowRateLimiter(long limit, long windowMs) {
        this(limit, windowMs, new ConcurrentHashMap<>());
    }

    private FixedWindowRateLimiter(long limit, long windowMs, ConcurrentMap<K, Window> map) {
        if (limit <= 0 || windowMs <= 0) {
            throw new IllegalArgumentException("limit & windowMs 必须 > 0");
        }
        this.limit = limit;
        this.windowMs = windowMs;
        this.windows = map;
    }

    @Override
    public boolean tryAcquire(K key) {
        long now = System.currentTimeMillis();
        // 1️⃣ 取或创建 Window（ConcurrentHashMap 的 computeIfAbsent）
        Window w = windows.computeIfAbsent(key, k -> new Window(limit, windowMs));

        // 2️⃣ 检查是否已经进入下一个窗口
        long elapsed = now - w.startTs;
        if (elapsed >= w.windowMs) {
            // 为了避免高并发下的竞争，这里使用 CAS 只在一次线程里 “重置”
            synchronized (w) {
                // double‑check 防止已被其他线程重置
                if (now - w.startTs >= w.windowMs) {
                    w.startTs = now;
                    w.counter.set(0L);
                }
            }
        }

        // 3️⃣ 判断并计数
        while (true) {
            long cur = w.counter.get();
            if (cur >= w.limit) {
                return false;               // 已达上限
            }
            if (w.counter.compareAndSet(cur, cur + 1)) {
                return true;                // 成功加 1
            }
            // CAS 失败 → 重试（并发争抢同一个窗口的情况很少）
        }
    }

}
